/*-------------------------------------------------------------------------
 *
 * dsa.c
 *	  动态共享内存区域。
 *
 * 此模块提供基于 DSM 段的动态共享内存区域。虽然 dsm.c 允许创建和共享后端之间的共享内存段，但它不旨在处理小对象。
 * DSA 区域是一个共享内存堆，通常由一个或多个 DSM 段支持，可以使用 dsa_allocate() 和 dsa_free() 进行内存分配。
 * 另一个选项是在已有的共享内存中创建，包括一个 DSM 段，然后根据需要创建额外的 DSM 段。
 * 与常规系统堆不同，它处理的伪指针必须在解引用之前转换为后端本地指针。然而，这些伪指针可以与其他后端共享，并可用于构造共享数据结构。
 *
 * 每个 DSA 区域管理一组 DSM 段，根据需要添加新段，并在不再需要时将其分离。每个段包含多个 4KB 页面、一个用于跟踪连续空闲页面的空闲页面管理器，以及用于跟踪在每个页面上分配的对象来源的页面映射。
 * 超过 8KB 的分配请求通过选择一个段并在其空闲页面管理器中寻找连续的空闲页面来处理。对于较小的请求，通过具有各种大小的对象池来处理。每个池由多个 16 页（64KB）超级块组成，以与大对象相同的方式分配。大对象和新超级块的分配通过一个单独的 LWLock 进行序列化，而来自现有超级块的小对象的分配则使用每个池的一个 LWLock。目前每个大小类有一个池，因此有一个锁。每个核心池以增加并发性和减少导致的碎片的策略是未来研究的方向。每个超级块用一个“跨度”进行管理，该跨度跟踪超级块的空闲列表。空闲请求通过查看页面映射以查找某个地址是从哪个跨度分配的，因此小对象可以返回到适当的空闲列表，大对象页面可以直接返回到空闲页面映射。当分配时，选择段和超级块的简单启发式尝试鼓励占用内存集中，以增加整个超级块变为空并返回到空闲页面管理器的可能性，以及使整个段变为空并返回到操作系统的可能性。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/mmgr/dsa.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "port/atomics.h"
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/shmem.h"
#include "utils/dsa.h"
#include "utils/freepage.h"
#include "utils/memutils.h"

/*
 * 由 dsa_create 创建的 dsa_area 支持的初始 DSM 段的大小。
 * 创建一些此大小的段后，我们将加倍此大小，依此类推。
 * 如果有必要，可以创建更大的段以满足大请求。
 */
#define DSA_INITIAL_SEGMENT_SIZE ((size_t) (1 * 1024 * 1024))

/*
 * 在我们加倍段大小之前，要创建的段数。如果这个数量
 * 低，则在最大的段中可能会有大量的浪费空间。
 * 如果数量高，则我们有风险耗尽段插槽（参见
 * dsm.c 对总段数的限制），或者在使用小指针时
 * 限制区域可以管理的总大小。
 */
#define DSA_NUM_SEGMENTS_AT_EACH_SIZE 2

/*
 * 表示 dsa_pointer 的偏移部分所用的位数。
 * 这控制段的最大大小、最大可能的
 * 分配大小以及每个区域的最大段数。
 */
#if SIZEOF_DSA_POINTER == 4
#define DSA_OFFSET_WIDTH 27		/* 32 个段，大小最大为 128MB */
#else
#define DSA_OFFSET_WIDTH 40		/* 1024 个段，大小最大为 1TB */
#endif

/*
 * 一个区域可以拥有的 DSM 段的最大数量，由剩余的
 * 位数决定（但最大为 1024）。
 */
#define DSA_MAX_SEGMENTS \
	Min(1024, (1 << ((SIZEOF_DSA_POINTER * 8) - DSA_OFFSET_WIDTH)))

/* 提取 dsa_pointer 偏移的位掩码。 */
#define DSA_OFFSET_BITMASK (((dsa_pointer) 1 << DSA_OFFSET_WIDTH) - 1)

/* DSM 段的最大大小。 */
#define DSA_MAX_SEGMENT_SIZE ((size_t) 1 << DSA_OFFSET_WIDTH)

/* 每个常规超级块的页面数（见 FPM_PAGE_SIZE）。 */
#define DSA_PAGES_PER_SUPERBLOCK		16

/*
 * 用作后续属于 DSA 区域的 DSM 段的有效性检查的魔法数字
 * （此数字将与区域句柄和段索引进行异或运算）。
 */
#define DSA_SEGMENT_HEADER_MAGIC 0x0ce26608

/* 基于段号和偏移量构建 dsa_pointer。 */
#define DSA_MAKE_POINTER(segment_number, offset) \
	(((dsa_pointer) (segment_number) << DSA_OFFSET_WIDTH) | (offset))

/* 从 dsa_pointer 中提取段号。 */
#define DSA_EXTRACT_SEGMENT_NUMBER(dp) ((dp) >> DSA_OFFSET_WIDTH)

 /* 从 dsa_pointer 中提取偏移量。 */
#define DSA_EXTRACT_OFFSET(dp) ((dp) & DSA_OFFSET_BITMASK)

 /* 用于索引段索引的类型（零基）。 */
typedef size_t dsa_segment_index;

 /* 表示“无”或“结束”的 dsa_segment_index 哨兵值。 */
#define DSA_SEGMENT_INDEX_NONE (~(dsa_segment_index)0)

 /* 我们有多少个段的桶？ 这些桶用于按其最大连续的空页数量对段进行分类。 */
#define DSA_NUM_SEGMENT_BINS 16

 /* 哪个桶包含可能有 n 个连续空页的段？查看较低桶中的段是没有意义的；它们肯定无法满足 n 个空页的请求。 */
#define contiguous_pages_to_segment_bin(n) Min(fls(n), DSA_NUM_SEGMENT_BINS - 1)

 /* 访问锁的宏。 */
#define DSA_AREA_LOCK(area) (&area->control->lock)
#define DSA_SCLASS_LOCK(area, sclass) (&area->control->pools[sclass].lock)

 /* 单个段的头部。 这位于每个 DSM 段的开始，由 DSA 区域拥有，包括第一个段（在这里它作为 dsa_area_control 结构的一部分出现）。 */
typedef struct
{
	 /* 效验检查魔法值。 */
	uint32		magic;
	 /* 该段中的页面总数（不包括元数据区域）。 */
	size_t		usable_pages;
	 /* 该段的总大小（以字节为单位）。 */
	size_t		size;

	 /* 在同一段桶中，前一个段的索引，或 DSA_SEGMENT_INDEX_NONE 如果这是第一个段。 */
	dsa_segment_index prev;

	 /* 在同一段桶中，后一个段的索引，或 DSA_SEGMENT_INDEX_NONE 如果这是最后一个段。 */
	dsa_segment_index next;
	 /* 包含此段的桶的索引。 */
	size_t		bin;

	 /* 一个标志，用于指示该段正被返回给操作系统并已被解锁。 */
	bool		freed;
} dsa_segment_header;

 /* 一个超级块的元数据。 */ 
typedef struct
{
	dsa_pointer pool;			 /* 对于大多数块，跨度对象存储在外部；也就是说，跨度对象并未存储在块本身内。 但作为例外，对于“跨度的跨度”，跨度对象是以“内联”方式存储的。 分配始终恰好是一个页面，dsa_area_span 对象位于该页面的开始。 大小类别是 DSA_SCLASS_BLOCK_OF_SPANS，剩余字段的使用方式与普通块相同。 我们无法从普通超级块分配跨度，因为创建普通超级块需要我们首先能够分配一个跨度。 以这种方式避免了这种循环性。 */
	dsa_pointer prevspan;		 /* 包含池。 */
	dsa_pointer nextspan;		 /* 前一个跨度。 */
	dsa_pointer start;			 /* 下一个跨度。 */
	size_t		npages;			 /* 起始地址。 */
	uint16		size_class;		 /* 跨度的长度（以页为单位）。 */
	uint16		ninitialized;	 /* 大小类别。 */
	uint16		nallocatable;	 /* 迄今为止分配的最大对象数量。 */
	uint16		firstfree;		 /* 当前可分配的对象数量。 */
	uint16		nmax;			 /* 自由列表上的第一个对象。 */
	uint16		fclass;			 /* 迄今为止可能的最大对象数量。 */
} dsa_area_span;

 /* 当前的满额类别。 */
#define NextFreeObjectIndex(object) (* (uint16 *) (object))

 /* 给定指向跨度中对象的指针，访问同一跨度中下一个空闲对象的索引（即在跨度的自由列表中）作为 L 值。 */
static const uint16 dsa_size_classes[] = {
	sizeof(dsa_area_span), 0,	 /* 小型分配通过将单个内存块划分为许多相同大小的小对象来处理。 可能的分配大小由以下数组定义。 较大尺寸类别的间隔比较小尺寸类别要宽。 为了避免空间浪费，我们对大于 1kB 的尺寸类别进行了调整：基于我们计划分配 64kB 块的知识，我们将最大对象大小提升到仍然允许我们将相同数量的对象放入一个块的最大 8 字节倍数。 */ 
	8, 16, 24, 32, 40, 48, 56, 64,	/* 8 个类，每个类间隔 8 字节 */
	80, 96, 112, 128,			/* 4 个类，每个类间隔 16 字节 */
	160, 192, 224, 256,			/* 4 个类，每个类间隔 32 字节 */
	320, 384, 448, 512,			/* 4 个类，每个类间隔 64 字节 */
	640, 768, 896, 1024,		/* 4 个类，每个类间隔 128 字节 */
	1280, 1560, 1816, 2048,		/* 4 个类，每个类间隔 ~256 字节 */
	2616, 3120, 3640, 4096,		/* 4 个类，每个类间隔 ~512 字节 */
	5456, 6552, 7280, 8192		/* 4 个类，每个类间隔 ~1024 字节 */
};
#define DSA_NUM_SIZE_CLASSES				lengthof(dsa_size_classes)

/* 特殊大小类。 */
#define DSA_SCLASS_BLOCK_OF_SPANS		0
#define DSA_SCLASS_SPAN_LARGE			1

/*
 * 以下查找表用于将小对象的大小（小于 1kB）映射到相应的大小类。要使用此表，
 * 将对象的大小向上舍入到下一个 8 字节的倍数，然后
 * 索引到此数组中。
 */
static const uint8 dsa_size_class_map[] = {
	2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, 11, 12, 12, 13, 13,
	14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 17,
	18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
	20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 21,
	22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
	23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
	24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
	25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25
};
#define DSA_SIZE_CLASS_MAP_QUANTUM	8

/*
 * 超块按满度归类。一般来说，每个满度类对应一个四分位数，但用于
 * 分配的块始终位于满度类 1 的列表头部，无论它实际上有多满。
 */
#define DSA_FULLNESS_CLASSES		4

/*
 * dsa_area_pool 表示一组给定大小类的对象。
 *
 * 或许同一大小类应该有多个池以避免争用，但目前只有一个！
 */
typedef struct
{
	/* 保护对该池访问的锁。 */
	LWLock		lock;
	/* 一组按满度排列的跨度链接列表。 */
	dsa_pointer spans[DSA_FULLNESS_CLASSES];
	/* 我们应该将其填充到缓存行边界吗？ */
} dsa_area_pool;

/*
 * 一个区域的控制块。它位于共享内存中，位于
 * 此区域控制的第一个 DSM 段的开头。
 */
typedef struct
{
	/* 第一个段的段头。 */
	dsa_segment_header segment_header;
	/* 此区域的句柄。 */
	dsa_handle	handle;
	/* 此区域拥有的段的句柄。 */
	dsm_handle	segment_handles[DSA_MAX_SEGMENTS];
	/* 按最大连续空闲页面运行排列的段列表。 */
	dsa_segment_index segment_bins[DSA_NUM_SEGMENT_BINS];
	/* 每个大小类的对象池。 */
	dsa_area_pool pools[DSA_NUM_SIZE_CLASSES];
	/* 所有活动段的总大小。 */
	size_t		total_segment_size;
	/* 我们允许的最大备份存储总大小。 */
	size_t		max_total_segment_size;
	/* 此区域历史中使用过的最高段索引。 */
	dsa_segment_index high_segment_index;
	/* 此区域的引用计数。 */
	int			refcnt;
	/* 一个标志，指示此区域已被固定。 */
	bool		pinned;
	/* 段被释放的次数。 */
	size_t		freed_segment_counter;
	/* LWLock 分段 ID。 */
	int			lwlock_tranche_id;
	/* 通用锁（保护除对象池之外的所有内容）。 */
	LWLock		lock;
} dsa_area_control;

/* 给定一个指向池的指针，找到一个 dsa_pointer。 */
#define DsaAreaPoolToDsaPointer(area, p)	\
	DSA_MAKE_POINTER(0, (char *) p - (char *) area->control)

/*
 * dsa_segment_map 存储在每个个体后端的后端私有内存中。它保存了该后端内段的基地址，以及段内关键对象的地址。那些地址可以从基地址推导出来，但保留它们是方便的。
 */
typedef struct
{
	dsm_segment *segment;		/* DSM 段 */
	char	   *mapped_address; /* 段映射的地址 */
	dsa_segment_header *header; /* 头部（与 mapped_address 相同） */
	FreePageManager *fpm;		/* 段内的空闲页管理器。 */
	dsa_pointer *pagemap;		/* 段内的页映射。 */
} dsa_segment_map;

/*
 * 每个后端的存储区域状态。后端通过创建一个区域或使用句柄附加到现有区域来获得其中一个。每个需要使用区域的进程使用自己的对象来跟踪段映射的位置。
 */
struct dsa_area
{
	/* 指向共享内存中控制对象的指针。 */
	dsa_area_control *control;

	/* 映射是否已被固定？ */
	bool		mapping_pinned;

	/*
	 * 此后端的段映射数组，按对应于 control->segment_handles 的段索引排序。该区域的一些段可能尚未在此后端映射，一些插槽可能已被释放并需要分离；这些操作按需进行。
	 */
	dsa_segment_map segment_maps[DSA_MAX_SEGMENTS];

	/* 此后端曾经映射的最高段索引。 */
	dsa_segment_index high_segment_index;

	/* 最后观察到的 freed_segment_counter。 */
	size_t		freed_segment_counter;
};

#define DSA_SPAN_NOTHING_FREE	((uint16) -1)
#define DSA_SUPERBLOCK_SIZE (DSA_PAGES_PER_SUPERBLOCK * FPM_PAGE_SIZE)

/* 给定指向 segment_map 的指针，获取段索引号。 */
#define get_segment_index(area, segment_map_ptr) \
	(segment_map_ptr - &area->segment_maps[0])

static void fc_init_span(dsa_area *fc_area, dsa_pointer fc_span_pointer,
					  dsa_area_pool *fc_pool, dsa_pointer fc_start, size_t fc_npages,
					  uint16 fc_size_class);
static bool fc_transfer_first_span(dsa_area *fc_area, dsa_area_pool *fc_pool,
								int fc_fromclass, int fc_toclass);
static inline dsa_pointer fc_alloc_object(dsa_area *fc_area, int fc_size_class);
static bool fc_ensure_active_superblock(dsa_area *fc_area, dsa_area_pool *fc_pool,
									 int fc_size_class);
static dsa_segment_map *get_segment_by_index(dsa_area *fc_area,
											 dsa_segment_index fc_index);
static void fc_destroy_superblock(dsa_area *fc_area, dsa_pointer fc_span_pointer);
static void fc_unlink_span(dsa_area *fc_area, dsa_area_span *fc_span);
static void add_span_to_fullness_class(dsa_area *fc_area, dsa_area_span *fc_span,
									   dsa_pointer fc_span_pointer, int fc_fclass);
static void fc_unlink_segment(dsa_area *fc_area, dsa_segment_map *fc_segment_map);
static dsa_segment_map *get_best_segment(dsa_area *fc_area, size_t fc_npages);
static dsa_segment_map *fc_make_new_segment(dsa_area *fc_area, size_t fc_requested_pages);
static dsa_area *fc_create_internal(void *fc_place, size_t fc_size,
								 int fc_tranche_id,
								 dsm_handle fc_control_handle,
								 dsm_segment *fc_control_segment);
static dsa_area *fc_attach_internal(void *fc_place, dsm_segment *fc_segment,
								 dsa_handle fc_handle);
static void fc_check_for_freed_segments(dsa_area *fc_area);
static void fc_check_for_freed_segments_locked(dsa_area *fc_area);
static void fc_rebin_segment(dsa_area *fc_area, dsa_segment_map *fc_segment_map);

/*
 * 在新的 DSM 段中创建一个新的共享区域。将根据需要分配进一步的 DSM 段以扩展可用空间。
 *
 * 我们无法在此函数中分配 LWLock tranche_id，因为 tranche ID 是稀缺资源；只有 64k 可用，因此在可能的情况下使用低数字很重要，并且我们没有回收它们的机制。因此，我们要求调用者提供一个。
 */
dsa_area * dsa_create(int fc_tranche_id)
{
	dsm_segment *fc_segment;
	dsa_area   *fc_area;

	/*
	 * 创建将保存共享控制对象和可用空间第一个段的 DSM 段。
	 */
	fc_segment = dsm_create(DSA_INITIAL_SEGMENT_SIZE, 0);

	/*
	 * 支持该区域的所有段都被固定，以便 DSA 可以显式控制其生命周期（否则，属于该区域的新创建段可能在唯一映射该段的后端结束时被释放，从而损坏该区域）。
	 */
	dsm_pin_segment(fc_segment);

	/* 创建一个新的 DSA 区域，在此段中包含控制对象。 */
	fc_area = fc_create_internal(dsm_segment_address(fc_segment),
						   DSA_INITIAL_SEGMENT_SIZE,
						   fc_tranche_id,
						   dsm_segment_handle(fc_segment), fc_segment);

	/* 当控制段分离时进行清理。 */
	on_dsm_detach(fc_segment, &dsa_on_dsm_detach_release_in_place,
				  PointerGetDatum(dsm_segment_address(fc_segment)));

	return fc_area;
}

/*
 * 在现有共享内存空间中创建一个新的共享区域，该空间可能是 DSM 或 Postmaster 初始化的内存。将根据需要分配 DSM 段以扩展可用空间，但可以使用 dsa_set_size_limit(area, size) 防止这一点，使用的大小与提供给 dsa_create_in_place 的大小相同。
 *
 * 以就地方式创建的区域必须最终由创建它们的后端及所有附加到它们的后端释放。可以通过 dsa_release_in_place 显式进行，或者在“place”碰巧位于预先存在的 DSM 段的特殊情况下，传入指向该段的指针，以便可以在包含的 DSM 段中注册分离钩子。
 *
 * 请参见 dsa_create() 以了解有关 tranche 参数的说明。
 */
dsa_area * dsa_create_in_place(void *fc_place, size_t fc_size,
					int fc_tranche_id, dsm_segment *fc_segment)
{
	dsa_area   *fc_area;

	fc_area = fc_create_internal(fc_place, fc_size, fc_tranche_id,
						   DSM_HANDLE_INVALID, NULL);

	/*
	 * 当控制段分离时进行清理，如果提供了包含的 DSM 段。
	 */
	if (fc_segment != NULL)
		on_dsm_detach(fc_segment, &dsa_on_dsm_detach_release_in_place,
					  PointerGetDatum(fc_place));

	return fc_area;
}

/*
 * 获取可以传递给其他进程的句柄，以便它们可以附加到给定区域。不能在用 dsa_create_in_place 创建的区域上调用。
 */
dsa_handle
dsa_get_handle(dsa_area *fc_area)
{
	Assert(fc_area->control->handle != DSM_HANDLE_INVALID);
	return fc_area->control->handle;
}

/*
 * 根据 dsa_get_handle 生成的句柄（可能在另一个进程中）附加到区域。该区域必须使用 dsa_create 创建（而不是 dsa_create_in_place）。
 */
dsa_area * dsa_attach(dsa_handle fc_handle)
{
	dsm_segment *fc_segment;
	dsa_area   *fc_area;

	/*
	 * 区域句柄实际上是第一个段的 DSM 段句柄，因此我们直接附加到该段。
	 */
	fc_segment = dsm_attach(fc_handle);
	if (fc_segment == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("could not attach to dynamic shared area")));

	fc_area = fc_attach_internal(dsm_segment_address(fc_segment), fc_segment, fc_handle);

	/* 当控制段分离时进行清理。 */
	on_dsm_detach(fc_segment, &dsa_on_dsm_detach_release_in_place,
				  PointerGetDatum(dsm_segment_address(fc_segment)));

	return fc_area;
}

/*
 * 附加到使用 dsa_create_in_place 创建的区域。调用者必须以某种方式知道创建区域时使用的内存位置，尽管在此进程中可能映射到不同的虚拟地址。
 *
 * 请参见 dsa_create_in_place 有关释放就地区域的说明，以及可以提供的可选“segment”参数，以允许自动释放，如果包含的内存恰好是 DSM 段。
 */
dsa_area * dsa_attach_in_place(void *fc_place, dsm_segment *fc_segment)
{
	dsa_area   *fc_area;

	fc_area = fc_attach_internal(fc_place, NULL, DSM_HANDLE_INVALID);

	/*
	 * 当控制段分离时进行清理，如果提供了包含的 DSM 段。
	 */
	if (fc_segment != NULL)
		on_dsm_detach(fc_segment, &dsa_on_dsm_detach_release_in_place,
					  PointerGetDatum(fc_place));

	return fc_area;
}


/*
 * 释放由 dsa_create_in_place 或 dsa_attach_in_place 产生的 DSA 区域。'segment' 参数被忽略，但提供了一个适合于 on_dsm_detach 的接口，方便希望在现有 DSM 段中创建 DSA 段并在包含的 DSM 段被分离时自动释放的用户。'place' 应该是创建区域的地址。
 *
 * 当提供段给 dsa_create_in_place 或 dsa_attach_in_place 时，此回调自动注册于包含就地区域控制对象的 DSM 段中，也适用于通过 dsa_create 创建的所有区域。
 */
void dsa_on_dsm_detach_release_in_place(dsm_segment *fc_segment, Datum fc_place)
{
	dsa_release_in_place(DatumGetPointer(fc_place));
}

/*
 * 释放由 dsa_create_in_place 或 dsa_attach_in_place 产生的 DSA 区域。'code' 参数被忽略，但提供了一个适合于 on_shmem_exit 或 before_shmem_exit 的接口，方便希望在共享内存中创建一个 DSA 段而不是 DSM 段的用户，并希望在后端退出时自动释放。'place' 应该是创建区域的地址。
 */
void dsa_on_shmem_exit_release_in_place(int fc_code, Datum fc_place)
{
	dsa_release_in_place(DatumGetPointer(fc_place));
}

/*
 * 释放由 dsa_create_in_place 或 dsa_attach_in_place 产生的 DSA 区域。最好使用其中一个 'dsa_on_XXX' 回调，以便自动管理，因为未能释放在地创建的区域会导致其段永久泄漏。
 *
 * 这也会自动调用，处理由 dsa_create 或 dsa_attach 产生的区域，这是一种实现细节。
 */
void dsa_release_in_place(void *fc_place)
{
	dsa_area_control *fc_control = (dsa_area_control *) fc_place;
	int			fc_i;

	LWLockAcquire(&fc_control->lock, LW_EXCLUSIVE);
	Assert(fc_control->segment_header.magic ==
		   (DSA_SEGMENT_HEADER_MAGIC ^ fc_control->handle ^ 0));
	Assert(fc_control->refcnt > 0);
	if (--fc_control->refcnt == 0)
	{
		for (fc_i = 0; fc_i <= fc_control->high_segment_index; ++fc_i)
		{
			dsm_handle	fc_handle;

			fc_handle = fc_control->segment_handles[fc_i];
			if (fc_handle != DSM_HANDLE_INVALID)
				dsm_unpin_segment(fc_handle);
		}
	}
	LWLockRelease(&fc_control->lock);
}

/*
 * 保持 DSA 区域附加直到会话结束或显式分离。
 *
 * 默认情况下，区域由当前资源所有者拥有，这意味着当该作用域结束时，它们会自动分离。
 */
void dsa_pin_mapping(dsa_area *fc_area)
{
	int			fc_i;

	Assert(!fc_area->mapping_pinned);
	fc_area->mapping_pinned = true;

	for (fc_i = 0; fc_i <= fc_area->high_segment_index; ++fc_i)
		if (fc_area->segment_maps[fc_i].segment != NULL)
			dsm_pin_mapping(fc_area->segment_maps[fc_i].segment);
}

/*
 * 在此存储区域分配内存。返回值是一个 dsa_pointer，可以传递给其他进程，并通过 dsa_get_address 转换为本地指针。'flags' 是一个位图，应根据以下值构造：
 *
 * DSA_ALLOC_HUGE 允许分配 >= 1GB 的内存。否则，此类分配将导致错误。
 *
 * DSA_ALLOC_NO_OOM 导致当没有可用内存或由 dsa_set_size_limit 确定的大小限制被超出时，该函数返回 InvalidDsaPointer。否则，此类分配将导致错误。
 *
 * DSA_ALLOC_ZERO 会将分配的内存置零。否则，新分配内存的内容是不确定的。
 *
 * 这些标志对应于 MemoryContextAllocExtended() 中使用的同名标志。另请参见宏 dsa_allocate 和 dsa_allocate0，它们扩展为使用常用标志调用此函数。
 */
dsa_pointer
dsa_allocate_extended(dsa_area *fc_area, size_t fc_size, int fc_flags)
{
	uint16		fc_size_class;
	dsa_pointer fc_start_pointer;
	dsa_segment_map *fc_segment_map;
	dsa_pointer fc_result;

	Assert(fc_size > 0);

	/* 对巨大的单次分配大小进行健康检查。 */
	if (((fc_flags & DSA_ALLOC_HUGE) != 0 && !AllocHugeSizeIsValid(fc_size)) ||
		((fc_flags & DSA_ALLOC_HUGE) == 0 && !AllocSizeIsValid(fc_size)))
		elog(ERROR, "invalid DSA memory alloc request size %zu", fc_size);

	/*
	 * 如果大于最大的大小类，那就从空闲页面管理器中获取一段页面，
	 * 而不是从池中分配对象。仍然会有一个跨度，但这是一个特殊的
	 * 跨度类，负责管理整个分配，并在调用 dsa_free 时将所有页面
	 * 归还给空闲页面管理器。
	 */
	if (fc_size > dsa_size_classes[lengthof(dsa_size_classes) - 1])
	{
		size_t		fc_npages = fpm_size_to_pages(fc_size);
		size_t		fc_first_page;
		dsa_pointer fc_span_pointer;
		dsa_area_pool *fc_pool = &fc_area->control->pools[DSA_SCLASS_SPAN_LARGE];

		/* 获取一个跨度对象。 */
		fc_span_pointer = fc_alloc_object(fc_area, DSA_SCLASS_BLOCK_OF_SPANS);
		if (!DsaPointerIsValid(fc_span_pointer))
		{
			/* 除非要求不这样做，否则抛出错误。 */
			if ((fc_flags & DSA_ALLOC_NO_OOM) == 0)
				ereport(ERROR,
						(errcode(ERRCODE_OUT_OF_MEMORY),
						 errmsg("out of memory"),
						 errdetail("Failed on DSA request of size %zu.",
								   fc_size)));
			return InvalidDsaPointer;
		}

		LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);

		/* 找到一个可以分配的段。 */
		fc_segment_map = get_best_segment(fc_area, fc_npages);
		if (fc_segment_map == NULL)
			fc_segment_map = fc_make_new_segment(fc_area, fc_npages);
		if (fc_segment_map == NULL)
		{
			/* 不能再创建更多段：游戏结束。 */
			LWLockRelease(DSA_AREA_LOCK(fc_area));
			dsa_free(fc_area, fc_span_pointer);

			/* 除非要求不这样做，否则抛出错误。 */
			if ((fc_flags & DSA_ALLOC_NO_OOM) == 0)
				ereport(ERROR,
						(errcode(ERRCODE_OUT_OF_MEMORY),
						 errmsg("out of memory"),
						 errdetail("Failed on DSA request of size %zu.",
								   fc_size)));
			return InvalidDsaPointer;
		}

		/*
		 * 请求空闲页面管理器获取一段页面。 这应该始终
		 * 成功，因为 get_best_segment 和 make_new_segment 应该
		 * 仅在实际包含足够的连续空闲空间时返回非 NULL 指针。
		 * 如果失败，说明我们的后端私有状态出现问题，因此使用
		 * FATAL 来终止进程。
		 */
		if (!FreePageManagerGet(fc_segment_map->fpm, fc_npages, &fc_first_page))
			elog(FATAL,
				 "dsa_allocate could not find %zu free pages", fc_npages);
		LWLockRelease(DSA_AREA_LOCK(fc_area));

		fc_start_pointer = DSA_MAKE_POINTER(get_segment_index(fc_area, fc_segment_map),
										 fc_first_page * FPM_PAGE_SIZE);

		/* 初始化跨度和页面映射。 */
		LWLockAcquire(DSA_SCLASS_LOCK(fc_area, DSA_SCLASS_SPAN_LARGE),
					  LW_EXCLUSIVE);
		fc_init_span(fc_area, fc_span_pointer, fc_pool, fc_start_pointer, fc_npages,
				  DSA_SCLASS_SPAN_LARGE);
		fc_segment_map->pagemap[fc_first_page] = fc_span_pointer;
		LWLockRelease(DSA_SCLASS_LOCK(fc_area, DSA_SCLASS_SPAN_LARGE));

		/* 如果请求，初始化内存为零。 */
		if ((fc_flags & DSA_ALLOC_ZERO) != 0)
			memset(dsa_get_address(fc_area, fc_start_pointer), 0, fc_size);

		return fc_start_pointer;
	}

	/* 将分配映射到一个大小类。 */
	if (fc_size < lengthof(dsa_size_class_map) * DSA_SIZE_CLASS_MAP_QUANTUM)
	{
		int			fc_mapidx;

		/* 对于较小的尺寸，我们有一个查找表... */
		fc_mapidx = ((fc_size + DSA_SIZE_CLASS_MAP_QUANTUM - 1) /
				  DSA_SIZE_CLASS_MAP_QUANTUM) - 1;
		fc_size_class = dsa_size_class_map[fc_mapidx];
	}
	else
	{
		uint16		fc_min;
		uint16		fc_max;

		/* ... 对于其余部分，我们通过二分搜索。 */
		fc_min = dsa_size_class_map[lengthof(dsa_size_class_map) - 1];
		fc_max = lengthof(dsa_size_classes) - 1;

		while (fc_min < fc_max)
		{
			uint16		fc_mid = (fc_min + fc_max) / 2;
			uint16		fc_class_size = dsa_size_classes[fc_mid];

			if (fc_class_size < fc_size)
				fc_min = fc_mid + 1;
			else
				fc_max = fc_mid;
		}

		fc_size_class = fc_min;
	}
	Assert(fc_size <= dsa_size_classes[fc_size_class]);
	Assert(fc_size_class == 0 || fc_size > dsa_size_classes[fc_size_class - 1]);

	/* 尝试从适当的池中分配对象。 */
	fc_result = fc_alloc_object(fc_area, fc_size_class);

	/* 检查分配失败。 */
	if (!DsaPointerIsValid(fc_result))
	{
		/* 除非要求不这样做，否则抛出错误。 */
		if ((fc_flags & DSA_ALLOC_NO_OOM) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory"),
					 errdetail("Failed on DSA request of size %zu.", fc_size)));
		return InvalidDsaPointer;
	}

	/* 如果请求，初始化内存为零。 */
	if ((fc_flags & DSA_ALLOC_ZERO) != 0)
		memset(dsa_get_address(fc_area, fc_result), 0, fc_size);

	return fc_result;
}

/*
 * 释放通过 dsa_allocate 获取的内存。
 */
void dsa_free(dsa_area *fc_area, dsa_pointer fc_dp)
{
	dsa_segment_map *fc_segment_map;
	int			fc_pageno;
	dsa_pointer fc_span_pointer;
	dsa_area_span *fc_span;
	char	   *fc_superblock;
	char	   *fc_object;
	size_t		fc_size;
	int			fc_size_class;

	/* 确保我们在 'dp' 所指的插槽中没有过时的段。 */
	fc_check_for_freed_segments(fc_area);

	/* 定位对象、跨度和池。 */
	fc_segment_map = get_segment_by_index(fc_area, DSA_EXTRACT_SEGMENT_NUMBER(fc_dp));
	fc_pageno = DSA_EXTRACT_OFFSET(fc_dp) / FPM_PAGE_SIZE;
	fc_span_pointer = fc_segment_map->pagemap[fc_pageno];
	fc_span = dsa_get_address(fc_area, fc_span_pointer);
	fc_superblock = dsa_get_address(fc_area, fc_span->start);
	fc_object = dsa_get_address(fc_area, fc_dp);
	fc_size_class = fc_span->size_class;
	fc_size = dsa_size_classes[fc_size_class];

	/*
	 * 大对象的特殊情况，它们位于一个特殊的跨度中：我们将
	 * 这些页面直接返回给空闲页面管理器并释放这个跨度。
	 */
	if (fc_span->size_class == DSA_SCLASS_SPAN_LARGE)
	{

#ifdef CLOBBER_FREED_MEMORY
		memset(fc_object, 0x7f, fc_span->npages * FPM_PAGE_SIZE);
#endif

		/* 将页面返回给空闲页面管理器。 */
		LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
		FreePageManagerPut(fc_segment_map->fpm,
						   DSA_EXTRACT_OFFSET(fc_span->start) / FPM_PAGE_SIZE,
						   fc_span->npages);

		/* 如有必要，将段移动到适当的框中。 */
		fc_rebin_segment(fc_area, fc_segment_map);
		LWLockRelease(DSA_AREA_LOCK(fc_area));

		/* 解除链接跨度。 */
		LWLockAcquire(DSA_SCLASS_LOCK(fc_area, DSA_SCLASS_SPAN_LARGE),
					  LW_EXCLUSIVE);
		fc_unlink_span(fc_area, fc_span);
		LWLockRelease(DSA_SCLASS_LOCK(fc_area, DSA_SCLASS_SPAN_LARGE));
		/* 释放跨度对象，以便可以重用。 */
		dsa_free(fc_area, fc_span_pointer);
		return;
	}

#ifdef CLOBBER_FREED_MEMORY
	memset(fc_object, 0x7f, fc_size);
#endif

	LWLockAcquire(DSA_SCLASS_LOCK(fc_area, fc_size_class), LW_EXCLUSIVE);

	/* 将对象放入跨度的空闲链表中。 */
	Assert(fc_object >= fc_superblock);
	Assert(fc_object < fc_superblock + DSA_SUPERBLOCK_SIZE);
	Assert((fc_object - fc_superblock) % fc_size == 0);
	NextFreeObjectIndex(fc_object) = fc_span->firstfree;
	fc_span->firstfree = (fc_object - fc_superblock) / fc_size;
	++fc_span->nallocatable;

	/*
	 * 查看跨度是否需要移动到不同的满度类，或者被释放
	 * 以便其页面可以返回给段。
	 */
	if (fc_span->nallocatable == 1 && fc_span->fclass == DSA_FULLNESS_CLASSES - 1)
	{
		/*
		 * 块完全满并位于最高编号的满度类中，该类永远不
		 * 会被扫描以查找空闲块。我们必须将其移动到下一个
		 * 较低的满度类。
		 */
		fc_unlink_span(fc_area, fc_span);
		add_span_to_fullness_class(fc_area, fc_span, fc_span_pointer,
								   DSA_FULLNESS_CLASSES - 2);

		/*
		 * 如果这是唯一的跨度，并且没有活动的跨度，那么
		 * 我们应该将这个跨度移动到满度类 1。 （否则，如果
		 * 你恰好分配了唯一跨度中的所有对象，它将移动到
		 * 类 3，然后你释放它们全部，它移动到 2，然后被
		 * 归还，导致没有活动跨度）。
		 */
	}
	else if (fc_span->nallocatable == fc_span->nmax &&
			 (fc_span->fclass != 1 || fc_span->prevspan != InvalidDsaPointer))
	{
		/*
		 * 整个块是空闲的，并且它不是该大小类的活动块。
		 * 将内存返回给空闲页面管理器。我们不这样做是为了
		 * 防止滞后：如果我们反复分配和释放活动块中的唯一
		 * 块，每次都解除分配和重新分配块将是非常低效的。
		 */
		fc_destroy_superblock(fc_area, fc_span_pointer);
	}

	LWLockRelease(DSA_SCLASS_LOCK(fc_area, fc_size_class));
}

/*
 * 获取 dsa_pointer 的后端本地地址。 'dp' 必须指向
 * 由给定区域分配的内存（可能在另一个进程中），并且
 * 尚未被释放。这可能会在需要时将段映射到当前进程，
 * 并可能导致释放的段被解除映射。
 */
void * dsa_get_address(dsa_area *fc_area, dsa_pointer fc_dp)
{
	dsa_segment_index fc_index;
	size_t		fc_offset;

	/* 将 InvalidDsaPointer 转换为 NULL。 */
	if (!DsaPointerIsValid(fc_dp))
		return NULL;

	/* 处理任何请求以从释放的段中分离。 */
	fc_check_for_freed_segments(fc_area);

	/* 将 dsa_pointer 拆分为其组件。 */
	fc_index = DSA_EXTRACT_SEGMENT_NUMBER(fc_dp);
	fc_offset = DSA_EXTRACT_OFFSET(fc_dp);
	Assert(fc_index < DSA_MAX_SEGMENTS);

	/* 检查我们是否需要将此段映射进来。 */
	if (unlikely(fc_area->segment_maps[fc_index].mapped_address == NULL))
	{
		/* 调用以获取效果（我们不需要结果）。 */
		get_segment_by_index(fc_area, fc_index);
	}

	return fc_area->segment_maps[fc_index].mapped_address + fc_offset;
}

/* 
 * 钉住这个区域，以便即使所有后端从中分离，它仍然会继续存在。
 * 在这种情况下，如果某处已经记录了句柄，该区域仍然可以重新附加。
 */
void dsa_pin(dsa_area *fc_area)
{
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	if (fc_area->control->pinned)
	{
		LWLockRelease(DSA_AREA_LOCK(fc_area));
		elog(ERROR, "dsa_area already pinned");
	}
	fc_area->control->pinned = true;
	++fc_area->control->refcnt;
	LWLockRelease(DSA_AREA_LOCK(fc_area));
}

/* 
 * 撤销 dsa_pin 的效果，以便在没有后端附加到它时可以释放给定区域。
 * 仅在调用了 dsa_pin 的情况下可以调用。
 */
void dsa_unpin(dsa_area *fc_area)
{
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	Assert(fc_area->control->refcnt > 1);
	if (!fc_area->control->pinned)
	{
		LWLockRelease(DSA_AREA_LOCK(fc_area));
		elog(ERROR, "dsa_area not pinned");
	}
	fc_area->control->pinned = false;
	--fc_area->control->refcnt;
	LWLockRelease(DSA_AREA_LOCK(fc_area));
}

/* 
 * 设置该区域的总大小限制。每当需要从操作系统分配新的段时，
 * 将检查该限制。如果新的大小限制已经超过，则此限制没有即时效果。
 * 
 * 注意，当段已被释放，但尚未被所有附加到它们的后端分离时，
 * 总虚拟内存使用量可能暂时大于此限制。
 */
void dsa_set_size_limit(dsa_area *fc_area, size_t fc_limit)
{
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	fc_area->control->max_total_segment_size = fc_limit;
	LWLockRelease(DSA_AREA_LOCK(fc_area));
}

/* 
 * 积极释放所有备用内存，希望将 DSM 段返回给操作系统。
 */
void dsa_trim(dsa_area *fc_area)
{
	int			fc_size_class;

	/* 
	 * 以反向池顺序修剪，以便在处理所有其他池时最后到达 spans-of-spans，
	 * 以防任何在此期间变得完全空闲。
	 */
	for (fc_size_class = DSA_NUM_SIZE_CLASSES - 1; fc_size_class >= 0; --fc_size_class)
	{
		dsa_area_pool *fc_pool = &fc_area->control->pools[fc_size_class];
		dsa_pointer fc_span_pointer;

		if (fc_size_class == DSA_SCLASS_SPAN_LARGE)
		{
			/* 大对象释放已经积极返回段。 */
			continue;
		}

		/* 
		 * 仅搜索满度类 1。我们预计在这里找到一个完全空的超级块
		 * （其他满度类中的完全空超级块由 dsa_free 返回到空闲页面映射中）。
		 */
		LWLockAcquire(DSA_SCLASS_LOCK(fc_area, fc_size_class), LW_EXCLUSIVE);
		fc_span_pointer = fc_pool->spans[1];
		while (DsaPointerIsValid(fc_span_pointer))
		{
			dsa_area_span *fc_span = dsa_get_address(fc_area, fc_span_pointer);
			dsa_pointer fc_next = fc_span->nextspan;

			if (fc_span->nallocatable == fc_span->nmax)
				fc_destroy_superblock(fc_area, fc_span_pointer);

			fc_span_pointer = fc_next;
		}
		LWLockRelease(DSA_SCLASS_LOCK(fc_area, fc_size_class));
	}
}

/* 
 * 打印有关共享内存区域内部状态的调试信息。
 */
void dsa_dump(dsa_area *fc_area)
{
	size_t		fc_i,
				fc_j;

	/* 
	 * 注意：这提供了一致性的快照，因为它在进行过程中获取和释放了
	 * 各个锁...
	 */

	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	fc_check_for_freed_segments_locked(fc_area);
	fprintf(stderr, "dsa_area handle %x:\n", fc_area->control->handle);
	fprintf(stderr, "  max_total_segment_size: %zu\n",
			fc_area->control->max_total_segment_size);
	fprintf(stderr, "  total_segment_size: %zu\n",
			fc_area->control->total_segment_size);
	fprintf(stderr, "  refcnt: %d\n", fc_area->control->refcnt);
	fprintf(stderr, "  pinned: %c\n", fc_area->control->pinned ? 't' : 'f');
	fprintf(stderr, "  segment bins:\n");
	for (fc_i = 0; fc_i < DSA_NUM_SEGMENT_BINS; ++fc_i)
	{
		if (fc_area->control->segment_bins[fc_i] != DSA_SEGMENT_INDEX_NONE)
		{
			dsa_segment_index fc_segment_index;

			if (fc_i == 0)
				fprintf(stderr,
						"    segment bin %zu (no contiguous free pages):\n", fc_i);
			else
				fprintf(stderr,
						"    segment bin %zu (at least %d contiguous pages free):\n",
						fc_i, 1 << (fc_i - 1));
			fc_segment_index = fc_area->control->segment_bins[fc_i];
			while (fc_segment_index != DSA_SEGMENT_INDEX_NONE)
			{
				dsa_segment_map *fc_segment_map;

				fc_segment_map =
					get_segment_by_index(fc_area, fc_segment_index);

				fprintf(stderr,
						"      segment index %zu, usable_pages = %zu, "
						"contiguous_pages = %zu, mapped at %p\n",
						fc_segment_index,
						fc_segment_map->header->usable_pages,
						fpm_largest(fc_segment_map->fpm),
						fc_segment_map->mapped_address);
				fc_segment_index = fc_segment_map->header->next;
			}
		}
	}
	LWLockRelease(DSA_AREA_LOCK(fc_area));

	fprintf(stderr, "  pools:\n");
	for (fc_i = 0; fc_i < DSA_NUM_SIZE_CLASSES; ++fc_i)
	{
		bool		fc_found = false;

		LWLockAcquire(DSA_SCLASS_LOCK(fc_area, fc_i), LW_EXCLUSIVE);
		for (fc_j = 0; fc_j < DSA_FULLNESS_CLASSES; ++fc_j)
			if (DsaPointerIsValid(fc_area->control->pools[fc_i].spans[fc_j]))
				fc_found = true;
		if (fc_found)
		{
			if (fc_i == DSA_SCLASS_BLOCK_OF_SPANS)
				fprintf(stderr, "    pool for blocks of span objects:\n");
			else if (fc_i == DSA_SCLASS_SPAN_LARGE)
				fprintf(stderr, "    pool for large object spans:\n");
			else
				fprintf(stderr,
						"    pool for size class %zu (object size %hu bytes):\n",
						fc_i, dsa_size_classes[fc_i]);
			for (fc_j = 0; fc_j < DSA_FULLNESS_CLASSES; ++fc_j)
			{
				if (!DsaPointerIsValid(fc_area->control->pools[fc_i].spans[fc_j]))
					fprintf(stderr, "      fullness class %zu is empty\n", fc_j);
				else
				{
					dsa_pointer fc_span_pointer = fc_area->control->pools[fc_i].spans[fc_j];

					fprintf(stderr, "      fullness class %zu:\n", fc_j);
					while (DsaPointerIsValid(fc_span_pointer))
					{
						dsa_area_span *fc_span;

						fc_span = dsa_get_address(fc_area, fc_span_pointer);
						fprintf(stderr,
								"        span descriptor at "
								DSA_POINTER_FORMAT ", superblock at "
								DSA_POINTER_FORMAT
								", pages = %zu, objects free = %hu/%hu\n",
								fc_span_pointer, fc_span->start, fc_span->npages,
								fc_span->nallocatable, fc_span->nmax);
						fc_span_pointer = fc_span->nextspan;
					}
				}
			}
		}
		LWLockRelease(DSA_SCLASS_LOCK(fc_area, fc_i));
	}
}

/* 
 * 返回您可以成功提供给 dsa_create_in_place 的最小大小。 
 */
size_t dsa_minimum_size(void)
{
	size_t		fc_size;
	int			fc_pages = 0;

	fc_size = MAXALIGN(sizeof(dsa_area_control)) +
		MAXALIGN(sizeof(FreePageManager));

	/* 计算我们需要多少页，包括页面映射... */
	while (((fc_size + FPM_PAGE_SIZE - 1) / FPM_PAGE_SIZE) > fc_pages)
	{
		++fc_pages;
		fc_size += sizeof(dsa_pointer);
	}

	return fc_pages * FPM_PAGE_SIZE;
}

/* 
 * dsa_create 和 dsa_create_in_place 的核心功能。
 */
static dsa_area *
fc_create_internal(void *fc_place, size_t fc_size,
				int fc_tranche_id,
				dsm_handle fc_control_handle,
				dsm_segment *fc_control_segment)
{
	dsa_area_control *fc_control;
	dsa_area   *fc_area;
	dsa_segment_map *fc_segment_map;
	size_t		fc_usable_pages;
	size_t		fc_total_pages;
	size_t		fc_metadata_bytes;
	int			fc_i;

	/* 对我们所拥有的空间进行合理性检查。 */
	if (fc_size < dsa_minimum_size())
		elog(ERROR, "dsa_area space must be at least %zu, but %zu provided",
			 dsa_minimum_size(), fc_size);

	/* 现在弄清楚有多少空间是可用的 */
	fc_total_pages = fc_size / FPM_PAGE_SIZE;
	fc_metadata_bytes =
		MAXALIGN(sizeof(dsa_area_control)) +
		MAXALIGN(sizeof(FreePageManager)) +
		fc_total_pages * sizeof(dsa_pointer);
	/* 添加填充以达到下一个页面边界。 */
	if (fc_metadata_bytes % FPM_PAGE_SIZE != 0)
		fc_metadata_bytes += FPM_PAGE_SIZE - (fc_metadata_bytes % FPM_PAGE_SIZE);
	Assert(fc_metadata_bytes <= fc_size);
	fc_usable_pages = (fc_size - fc_metadata_bytes) / FPM_PAGE_SIZE;

	/* 
	 * 初始化位于空间起始位置的 dsa_area_control 对象。
	 */
	fc_control = (dsa_area_control *) fc_place;
	memset(fc_place, 0, sizeof(*fc_control));
	fc_control->segment_header.magic =
		DSA_SEGMENT_HEADER_MAGIC ^ fc_control_handle ^ 0;
	fc_control->segment_header.next = DSA_SEGMENT_INDEX_NONE;
	fc_control->segment_header.prev = DSA_SEGMENT_INDEX_NONE;
	fc_control->segment_header.usable_pages = fc_usable_pages;
	fc_control->segment_header.freed = false;
	fc_control->segment_header.size = DSA_INITIAL_SEGMENT_SIZE;
	fc_control->handle = fc_control_handle;
	fc_control->max_total_segment_size = (size_t) -1;
	fc_control->total_segment_size = fc_size;
	fc_control->segment_handles[0] = fc_control_handle;
	for (fc_i = 0; fc_i < DSA_NUM_SEGMENT_BINS; ++fc_i)
		fc_control->segment_bins[fc_i] = DSA_SEGMENT_INDEX_NONE;
	fc_control->refcnt = 1;
	fc_control->lwlock_tranche_id = fc_tranche_id;

	/* 
	 * 创建此后端将用于访问区域的 dsa_area 对象。
	 * 其他后端需要通过附加获得自己的 dsa_area 对象。
	 */
	fc_area = palloc(sizeof(dsa_area));
	fc_area->control = fc_control;
	fc_area->mapping_pinned = false;
	memset(fc_area->segment_maps, 0, sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
	fc_area->high_segment_index = 0;
	fc_area->freed_segment_counter = 0;
	LWLockInitialize(&fc_control->lock, fc_control->lwlock_tranche_id);
	for (fc_i = 0; fc_i < DSA_NUM_SIZE_CLASSES; ++fc_i)
		LWLockInitialize(DSA_SCLASS_LOCK(fc_area, fc_i),
						 fc_control->lwlock_tranche_id);

	/* 为此进程的映射设置段映射。 */
	fc_segment_map = &fc_area->segment_maps[0];
	fc_segment_map->segment = fc_control_segment;
	fc_segment_map->mapped_address = fc_place;
	fc_segment_map->header = (dsa_segment_header *) fc_place;
	fc_segment_map->fpm = (FreePageManager *)
		(fc_segment_map->mapped_address +
		 MAXALIGN(sizeof(dsa_area_control)));
	fc_segment_map->pagemap = (dsa_pointer *)
		(fc_segment_map->mapped_address +
		 MAXALIGN(sizeof(dsa_area_control)) +
		 MAXALIGN(sizeof(FreePageManager)));

	/* 设置空闲页面映射。 */
	FreePageManagerInitialize(fc_segment_map->fpm, fc_segment_map->mapped_address);
	/* 如果大小为 dsa_minimum_size()，则可用页面数可以为 0。 */

	if (fc_usable_pages > 0)
		FreePageManagerPut(fc_segment_map->fpm, fc_metadata_bytes / FPM_PAGE_SIZE,
						   fc_usable_pages);

	/* 将该段放入适当的箱中。 */
	fc_control->segment_bins[contiguous_pages_to_segment_bin(fc_usable_pages)] = 0;
	fc_segment_map->header->bin = contiguous_pages_to_segment_bin(fc_usable_pages);

	return fc_area;
}

/* 
 * dsa_attach 和 dsa_attach_in_place 的核心功能。
 */
static dsa_area *
fc_attach_internal(void *fc_place, dsm_segment *fc_segment, dsa_handle fc_handle)
{
	dsa_area_control *fc_control;
	dsa_area   *fc_area;
	dsa_segment_map *fc_segment_map;

	fc_control = (dsa_area_control *) fc_place;
	Assert(fc_control->handle == fc_handle);
	Assert(fc_control->segment_handles[0] == fc_handle);
	Assert(fc_control->segment_header.magic ==
		   (DSA_SEGMENT_HEADER_MAGIC ^ fc_handle ^ 0));

	/* 构建后端本地区域对象。 */
	fc_area = palloc(sizeof(dsa_area));
	fc_area->control = fc_control;
	fc_area->mapping_pinned = false;
	memset(&fc_area->segment_maps[0], 0,
		   sizeof(dsa_segment_map) * DSA_MAX_SEGMENTS);
	fc_area->high_segment_index = 0;

	/* 为此进程的映射设置段映射。 */
	fc_segment_map = &fc_area->segment_maps[0];
	fc_segment_map->segment = fc_segment; /* NULL 表示就地 */
	fc_segment_map->mapped_address = fc_place;
	fc_segment_map->header = (dsa_segment_header *) fc_segment_map->mapped_address;
	fc_segment_map->fpm = (FreePageManager *)
		(fc_segment_map->mapped_address + MAXALIGN(sizeof(dsa_area_control)));
	fc_segment_map->pagemap = (dsa_pointer *)
		(fc_segment_map->mapped_address + MAXALIGN(sizeof(dsa_area_control)) +
		 MAXALIGN(sizeof(FreePageManager)));

	/* 提高引用计数。 */
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	if (fc_control->refcnt == 0)
	{
		/* 我们无法附加到已被销毁的 DSA 区域。 */
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("could not attach to dynamic shared area")));
	}
	++fc_control->refcnt;
	fc_area->freed_segment_counter = fc_area->control->freed_segment_counter;
	LWLockRelease(DSA_AREA_LOCK(fc_area));

	return fc_area;
}

/* 
 * 向所指池的满度类 1 添加一个新跨度。
 */
static void fc_init_span(dsa_area *fc_area,
		  dsa_pointer fc_span_pointer,
		  dsa_area_pool *fc_pool, dsa_pointer fc_start, size_t fc_npages,
		  uint16 fc_size_class)
{
	dsa_area_span *fc_span = dsa_get_address(fc_area, fc_span_pointer);
	size_t		fc_obsize = dsa_size_classes[fc_size_class];

	/*
	 * 必须持有每池锁，因为我们正在操作此池的跨度列表。
	 */
	Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(fc_area, fc_size_class)));

	/* 将这个跨度推送到充满度类别1的跨度列表前面。 */
	if (DsaPointerIsValid(fc_pool->spans[1]))
	{
		dsa_area_span *fc_head = (dsa_area_span *)
		dsa_get_address(fc_area, fc_pool->spans[1]);

		fc_head->prevspan = fc_span_pointer;
	}
	fc_span->pool = DsaAreaPoolToDsaPointer(fc_area, fc_pool);
	fc_span->nextspan = fc_pool->spans[1];
	fc_span->prevspan = InvalidDsaPointer;
	fc_pool->spans[1] = fc_span_pointer;

	fc_span->start = fc_start;
	fc_span->npages = fc_npages;
	fc_span->size_class = fc_size_class;
	fc_span->ninitialized = 0;
	if (fc_size_class == DSA_SCLASS_BLOCK_OF_SPANS)
	{
		/*
		 * 一块跨度包含其自身的描述符，因此将一个对象标记为
		 * 已初始化，并将可分配对象的数量减少一个。
		 * 在这里这样做的副作用是将nmax也减少一个，
		 * 这对于确保我们在正确的时间释放这个对象很重要。
		 */
		fc_span->ninitialized = 1;
		fc_span->nallocatable = FPM_PAGE_SIZE / fc_obsize - 1;
	}
	else if (fc_size_class != DSA_SCLASS_SPAN_LARGE)
		fc_span->nallocatable = DSA_SUPERBLOCK_SIZE / fc_obsize;
	fc_span->firstfree = DSA_SPAN_NOTHING_FREE;
	fc_span->nmax = fc_span->nallocatable;
	fc_span->fclass = 1;
}

/*
 * 将一个充满度类别中的第一个跨度转移到另一个
 * 充满度类别的头部。
 */
static bool fc_transfer_first_span(dsa_area *fc_area,
					dsa_area_pool *fc_pool, int fc_fromclass, int fc_toclass)
{
	dsa_pointer fc_span_pointer;
	dsa_area_span *fc_span;
	dsa_area_span *fc_nextspan;

	/* 如果源列表为空，则无法执行此操作。 */
	fc_span_pointer = fc_pool->spans[fc_fromclass];
	if (!DsaPointerIsValid(fc_span_pointer))
		return false;

	/* 从源列表的头部移除跨度。 */
	fc_span = dsa_get_address(fc_area, fc_span_pointer);
	fc_pool->spans[fc_fromclass] = fc_span->nextspan;
	if (DsaPointerIsValid(fc_span->nextspan))
	{
		fc_nextspan = (dsa_area_span *)
			dsa_get_address(fc_area, fc_span->nextspan);
		fc_nextspan->prevspan = InvalidDsaPointer;
	}

	/* 将跨度添加到目标列表的头部。 */
	fc_span->nextspan = fc_pool->spans[fc_toclass];
	fc_pool->spans[fc_toclass] = fc_span_pointer;
	if (DsaPointerIsValid(fc_span->nextspan))
	{
		fc_nextspan = (dsa_area_span *)
			dsa_get_address(fc_area, fc_span->nextspan);
		fc_nextspan->prevspan = fc_span_pointer;
	}
	fc_span->fclass = fc_toclass;

	return true;
}

/*
 * 从给定区域分配一个所请求大小类别的对象。
 */
static inline dsa_pointer
fc_alloc_object(dsa_area *fc_area, int fc_size_class)
{
	dsa_area_pool *fc_pool = &fc_area->control->pools[fc_size_class];
	dsa_area_span *fc_span;
	dsa_pointer fc_block;
	dsa_pointer fc_result;
	char	   *fc_object;
	size_t		fc_size;

	/*
	 * 尽管ensure_active_superblock可以在需要分配新的跨度时调用
	 * alloc_object，但这总是来自不同的池，
	 * 并且锁的获取顺序始终相同，因此在该函数执行期间持有
	 * 这个锁是可以的。
	 */
	Assert(!LWLockHeldByMe(DSA_SCLASS_LOCK(fc_area, fc_size_class)));
	LWLockAcquire(DSA_SCLASS_LOCK(fc_area, fc_size_class), LW_EXCLUSIVE);

	/*
	 * 如果没有活动的超级块，我们必须成功获得一个或
	 * 失败该请求。
	 */
	if (!DsaPointerIsValid(fc_pool->spans[1]) &&
		!fc_ensure_active_superblock(fc_area, fc_pool, fc_size_class))
	{
		fc_result = InvalidDsaPointer;
	}
	else
	{
		/*
		 * 在这一点上，充满度类别1中应该有一个块，并且它
		 * 不应该完全满。因此，我们可以从空闲列表中弹出一个对象
		 * 或者，如果失败，则初始化一个新的对象。
		 */
		Assert(DsaPointerIsValid(fc_pool->spans[1]));
		fc_span = (dsa_area_span *)
			dsa_get_address(fc_area, fc_pool->spans[1]);
		Assert(fc_span->nallocatable > 0);
		fc_block = fc_span->start;
		Assert(fc_size_class < DSA_NUM_SIZE_CLASSES);
		fc_size = dsa_size_classes[fc_size_class];
		if (fc_span->firstfree != DSA_SPAN_NOTHING_FREE)
		{
			fc_result = fc_block + fc_span->firstfree * fc_size;
			fc_object = dsa_get_address(fc_area, fc_result);
			fc_span->firstfree = NextFreeObjectIndex(fc_object);
		}
		else
		{
			fc_result = fc_block + fc_span->ninitialized * fc_size;
			++fc_span->ninitialized;
		}
		--fc_span->nallocatable;

		/* 如果现在已满，则将其移动到编号最高的充满度类别。 */
		if (fc_span->nallocatable == 0)
			fc_transfer_first_span(fc_area, fc_pool, 1, DSA_FULLNESS_CLASSES - 1);
	}

	Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(fc_area, fc_size_class)));
	LWLockRelease(DSA_SCLASS_LOCK(fc_area, fc_size_class));

	return fc_result;
}

/*
 * 确保一个活动的（即充满度类别1）超级块，除非所有现有
 * 超级块完全满，并且不能再分配更多。
 *
 * 充满度类别K的0..N大致上代表利用率百分比至少为K/N的块，
 * 但我们仅在编号最高的充满度类别上严格执行这一点，
 * 它总是包含那些完全满的块。此外，活跃块总是充满度类别1中的
 * 第一个块，可以具有高于通常允许的分配
 * 百分比；我们不会移动它，直到它完全满，然后它将转到
 * 编号最高的充满度类别。
 *
 * 可能看起来很奇怪，活跃块是充满度类别1的头部，而不是充满度类别0，
 * 但与其他分配器的经验表明，从一个适度
 * 满的块进行分配通常比从一个几乎空的块进行分配要好。在合理可能的
 * 范围内，我们希望避免在一个即将变为空的块中执行新的分配。
 */
static bool fc_ensure_active_superblock(dsa_area *fc_area, dsa_area_pool *fc_pool,
						 int fc_size_class)
{
	dsa_pointer fc_span_pointer;
	dsa_pointer fc_start_pointer;
	size_t		fc_obsize = dsa_size_classes[fc_size_class];
	size_t		fc_nmax;
	int			fc_fclass;
	size_t		fc_npages = 1;
	size_t		fc_first_page;
	size_t		fc_i;
	dsa_segment_map *fc_segment_map;

	Assert(LWLockHeldByMe(DSA_SCLASS_LOCK(fc_area, fc_size_class)));

	/*
	 * 计算适合此大小类别的块中可以容纳的对象数量。
	 * 跨度块仅为单页，且第一个
	 * 对象不可用，因为它描述的是跨度块
	 * 本身。
	 */
	if (fc_size_class == DSA_SCLASS_BLOCK_OF_SPANS)
		fc_nmax = FPM_PAGE_SIZE / fc_obsize - 1;
	else
		fc_nmax = DSA_SUPERBLOCK_SIZE / fc_obsize;

	/*
	 * 如果充满度类别1为空，尝试通过
	 * 扫描高编号的充满度类别来找到一个可以放入的跨度（不包括最后一个，
	 * 其块无疑都是完全满的）。
	 */
	for (fc_fclass = 2; fc_fclass < DSA_FULLNESS_CLASSES - 1; ++fc_fclass)
	{
		fc_span_pointer = fc_pool->spans[fc_fclass];

		while (DsaPointerIsValid(fc_span_pointer))
		{
			int			fc_tfclass;
			dsa_area_span *fc_span;
			dsa_area_span *fc_nextspan;
			dsa_area_span *fc_prevspan;
			dsa_pointer fc_next_span_pointer;

			fc_span = (dsa_area_span *)
				dsa_get_address(fc_area, fc_span_pointer);
			fc_next_span_pointer = fc_span->nextspan;

			/* 确定哪个充满度类别应该包含这个跨度。 */
			fc_tfclass = (fc_nmax - fc_span->nallocatable)
				* (DSA_FULLNESS_CLASSES - 1) / fc_nmax;

			/* 查找下一个跨度。 */
			if (DsaPointerIsValid(fc_span->nextspan))
				fc_nextspan = (dsa_area_span *)
					dsa_get_address(fc_area, fc_span->nextspan);
			else
				fc_nextspan = NULL;

			/*
			 * 如果利用率下降足够，以至于现在属于其他
			 * 充满度类别，则将其移动到那里。
			 */
			if (fc_tfclass < fc_fclass)
			{
				/* 从当前充满度类别列表中移除。 */
				if (fc_pool->spans[fc_fclass] == fc_span_pointer)
				{
					/* 它是头部；移除它。 */
					Assert(!DsaPointerIsValid(fc_span->prevspan));
					fc_pool->spans[fc_fclass] = fc_span->nextspan;
					if (fc_nextspan != NULL)
						fc_nextspan->prevspan = InvalidDsaPointer;
				}
				else
				{
					/* 它不是头部。 */
					Assert(DsaPointerIsValid(fc_span->prevspan));
					fc_prevspan = (dsa_area_span *)
						dsa_get_address(fc_area, fc_span->prevspan);
					fc_prevspan->nextspan = fc_span->nextspan;
				}
				if (fc_nextspan != NULL)
					fc_nextspan->prevspan = fc_span->prevspan;

				/* 将其推送到新的充满度类别列表的头部。 */
				fc_span->nextspan = fc_pool->spans[fc_tfclass];
				fc_pool->spans[fc_tfclass] = fc_span_pointer;
				fc_span->prevspan = InvalidDsaPointer;
				if (DsaPointerIsValid(fc_span->nextspan))
				{
					fc_nextspan = (dsa_area_span *)
						dsa_get_address(fc_area, fc_span->nextspan);
					fc_nextspan->prevspan = fc_span_pointer;
				}
				fc_span->fclass = fc_tfclass;
			}

			/* 前进到列表上的下一个跨度。 */
			fc_span_pointer = fc_next_span_pointer;
		}

		/* 如果找到合适的块，则立即停止。 */
		if (DsaPointerIsValid(fc_pool->spans[1]))
			return true;
	}

	/*
	 * 如果没有适合充满度类别1的块，从其他充满度类别中选择
	 * 一个并将其移动到那里，以便我们有一个分配目标。
	 * 我们的最后选择是转移一个几乎空的块（如果被单独放置，可能很快会
	 * 完全空），但即便如此，这总比失败要好，如果完全没有块有空闲空间，
	 * 我们将不得不这样做。
	 */
	Assert(!DsaPointerIsValid(fc_pool->spans[1]));
	for (fc_fclass = 2; fc_fclass < DSA_FULLNESS_CLASSES - 1; ++fc_fclass)
		if (fc_transfer_first_span(fc_area, fc_pool, fc_fclass, 1))
			return true;
	if (!DsaPointerIsValid(fc_pool->spans[1]) &&
		fc_transfer_first_span(fc_area, fc_pool, 0, 1))
		return true;

	/*
	 * 我们未能找到一个具有空闲对象的现有跨度，因此我们需要
	 * 分配一个新的超级块并构造一个新的跨度来管理它。
	 *
	 * 首先，获取一个dsa_area_span对象来描述新的超级块块
	 * ...除非此分配是用于dsa_area_span对象，在这种情况下
	 * 这肯定行不通。我们通过将描述块跨度的跨度内联存储来处理
	 * 这种情况。
	 */
	if (fc_size_class != DSA_SCLASS_BLOCK_OF_SPANS)
	{
		fc_span_pointer = fc_alloc_object(fc_area, DSA_SCLASS_BLOCK_OF_SPANS);
		if (!DsaPointerIsValid(fc_span_pointer))
			return false;
		fc_npages = DSA_PAGES_PER_SUPERBLOCK;
	}

	/* 查找或创建一个段并分配超级块。 */
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	fc_segment_map = get_best_segment(fc_area, fc_npages);
	if (fc_segment_map == NULL)
	{
		fc_segment_map = fc_make_new_segment(fc_area, fc_npages);
		if (fc_segment_map == NULL)
		{
			LWLockRelease(DSA_AREA_LOCK(fc_area));
			return false;
		}
	}

	/*
	 * 这不应该发生：get_best_segment()或make_new_segment()
	 * 承诺我们可以成功分配npages。
	 */
	if (!FreePageManagerGet(fc_segment_map->fpm, fc_npages, &fc_first_page))
		elog(FATAL,
			 "dsa_allocate could not find %zu free pages for superblock",
			 fc_npages);
	LWLockRelease(DSA_AREA_LOCK(fc_area));

	/* 计算超级块的起始位置。 */
	fc_start_pointer =
		DSA_MAKE_POINTER(get_segment_index(fc_area, fc_segment_map),
						 fc_first_page * FPM_PAGE_SIZE);

	
/*
	 * 如果这是一个区块跨度，从已分配的空间中直接提取描述符。
	 */
	if (fc_size_class == DSA_SCLASS_BLOCK_OF_SPANS)
	{
		/*
		 * 我们有一个指向段的指针。我们需要从段索引和段内偏移量构建一个 dsa_pointer。
		 */
		fc_span_pointer = fc_start_pointer;
	}

	/* 初始化跨度和页面映射。 */
	fc_init_span(fc_area, fc_span_pointer, fc_pool, fc_start_pointer, fc_npages, fc_size_class);
	for (fc_i = 0; fc_i < fc_npages; ++fc_i)
		fc_segment_map->pagemap[fc_first_page + fc_i] = fc_span_pointer;

	return true;
}

/*
 * 返回对应于给定段索引的段映射，如果有必要则映射该段。对于内部段的管理，这在持有区域锁的情况下调用。它也被 dsa_free 和 dsa_get_address 调用，而不进行任何锁定，依赖于它们拥有已知的有效段索引，并且它们始终调用 check_for_freed_segments，以确保首先解除占用同一插槽的任何已释放段。
 */
static dsa_segment_map * get_segment_by_index(dsa_area *fc_area, dsa_segment_index fc_index)
{
	if (unlikely(fc_area->segment_maps[fc_index].mapped_address == NULL))
	{
		dsm_handle	fc_handle;
		dsm_segment *fc_segment;
		dsa_segment_map *fc_segment_map;

		/*
		 * 如果我们是通过 dsa_free 或 dsa_get_address 到达的，那么在引用的段中必须至少分配了一个对象。否则，它们的调用者有双重释放或释放后访问的错误，我们无法检测到。因此，我们知道在不持有锁的情况下访问这个数组插槽是安全的；它不会在我们之下改变。此外，我们知道我们可以看到插槽的最新内容，如 check_for_freed_segments 中所解释的那样，这些函数在到达这里之前会调用它。
		 */
		fc_handle = fc_area->control->segment_handles[fc_index];

		/* 尝试访问未使用的插槽是错误的。 */
		if (fc_handle == DSM_HANDLE_INVALID)
			elog(ERROR,
				 "dsa_area could not attach to a segment that has been freed");

		fc_segment = dsm_attach(fc_handle);
		if (fc_segment == NULL)
			elog(ERROR, "dsa_area could not attach to segment");
		if (fc_area->mapping_pinned)
			dsm_pin_mapping(fc_segment);
		fc_segment_map = &fc_area->segment_maps[fc_index];
		fc_segment_map->segment = fc_segment;
		fc_segment_map->mapped_address = dsm_segment_address(fc_segment);
		fc_segment_map->header =
			(dsa_segment_header *) fc_segment_map->mapped_address;
		fc_segment_map->fpm = (FreePageManager *)
			(fc_segment_map->mapped_address +
			 MAXALIGN(sizeof(dsa_segment_header)));
		fc_segment_map->pagemap = (dsa_pointer *)
			(fc_segment_map->mapped_address +
			 MAXALIGN(sizeof(dsa_segment_header)) +
			 MAXALIGN(sizeof(FreePageManager)));

		/* 记住此后端曾经映射的最高索引。 */
		if (fc_area->high_segment_index < fc_index)
			fc_area->high_segment_index = fc_index;

		Assert(fc_segment_map->header->magic ==
			   (DSA_SEGMENT_HEADER_MAGIC ^ fc_area->control->handle ^ fc_index));
	}

	/*
	 * dsa_get_address() 和 dsa_free() 的调用者不持有区域锁，但如果地址不是有效的（即如果段可能已被释放，则它们试图使用悬挂指针），则调用代码中的错误和未定义行为。
	 *
	 * 对于持有区域锁以操作 segment_bins 列表的 dsa.c 代码，如果我们在这里到达已释放的段，这将是一个错误。在将其标记为释放后，任何后端对它应该做的唯一事情是解除映射，且它在到达这里以解析索引到 segment_map 之前，应该总是已在 check_for_freed_segments_locked() 中执行此操作。
	 *
	 * 无论如何，我们可以断言我们没有返回已释放的段。
	 */
	Assert(!fc_area->segment_maps[fc_index].header->freed);

	return &fc_area->segment_maps[fc_index];
}

/*
 * 将超级块返回给空闲页面管理器。如果底层段完全释放，则将其返回给操作系统。
 *
 * 必须持有适当的池锁。
 */
static void fc_destroy_superblock(dsa_area *fc_area, dsa_pointer fc_span_pointer)
{
	dsa_area_span *fc_span = dsa_get_address(fc_area, fc_span_pointer);
	int			fc_size_class = fc_span->size_class;
	dsa_segment_map *fc_segment_map;


	/* 从其满类列表中移除它。 */
	fc_unlink_span(fc_area, fc_span);

	/*
	 * 注意：在我们已经持有每个池锁的同时，这里我们获取区域锁。我们从不持有区域锁然后再获取池锁，否则我们可能会死锁。
	 */
	LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
	fc_check_for_freed_segments_locked(fc_area);
	fc_segment_map =
		get_segment_by_index(fc_area, DSA_EXTRACT_SEGMENT_NUMBER(fc_span->start));
	FreePageManagerPut(fc_segment_map->fpm,
					   DSA_EXTRACT_OFFSET(fc_span->start) / FPM_PAGE_SIZE,
					   fc_span->npages);
	/* 检查段现在是否完全释放。 */
	if (fpm_largest(fc_segment_map->fpm) == fc_segment_map->header->usable_pages)
	{
		dsa_segment_index fc_index = get_segment_index(fc_area, fc_segment_map);

		/* 如果它不是带有额外控制数据的段，释放它。 */
		if (fc_index != 0)
		{
			/*
			 * 将其返回给操作系统，并允许其他后端检测到
			 * 它们需要解除连接。
			 */
			fc_unlink_segment(fc_area, fc_segment_map);
			fc_segment_map->header->freed = true;
			Assert(fc_area->control->total_segment_size >=
				   fc_segment_map->header->size);
			fc_area->control->total_segment_size -=
				fc_segment_map->header->size;
			dsm_unpin_segment(dsm_segment_handle(fc_segment_map->segment));
			dsm_detach(fc_segment_map->segment);
			fc_area->control->segment_handles[fc_index] = DSM_HANDLE_INVALID;
			++fc_area->control->freed_segment_counter;
			fc_segment_map->segment = NULL;
			fc_segment_map->header = NULL;
			fc_segment_map->mapped_address = NULL;
		}
	}

	/* 如有必要，将段移动到适当的框中。 */
	if (fc_segment_map->header != NULL)
		fc_rebin_segment(fc_area, fc_segment_map);

	LWLockRelease(DSA_AREA_LOCK(fc_area));

	/*
	 * Span-of-spans 块在块本身内存储描述它们的跨度，因此释放存储隐式地也释放了描述符。如果这是任何其他类型的块，我们需要单独释放跨度对象。对 dsa_free 的这个递归调用将获取跨度池的锁。我们无法死锁，因为获取顺序始终是某个其他池，然后是跨度池。
	 */
	if (fc_size_class != DSA_SCLASS_BLOCK_OF_SPANS)
		dsa_free(fc_area, fc_span_pointer);
}

static void fc_unlink_span(dsa_area *fc_area, dsa_area_span *fc_span)
{
	if (DsaPointerIsValid(fc_span->nextspan))
	{
		dsa_area_span *fc_next = dsa_get_address(fc_area, fc_span->nextspan);

		fc_next->prevspan = fc_span->prevspan;
	}
	if (DsaPointerIsValid(fc_span->prevspan))
	{
		dsa_area_span *fc_prev = dsa_get_address(fc_area, fc_span->prevspan);

		fc_prev->nextspan = fc_span->nextspan;
	}
	else
	{
		dsa_area_pool *fc_pool = dsa_get_address(fc_area, fc_span->pool);

		fc_pool->spans[fc_span->fclass] = fc_span->nextspan;
	}
}

static void add_span_to_fullness_class(dsa_area *fc_area, dsa_area_span *fc_span,
						   dsa_pointer fc_span_pointer,
						   int fc_fclass)
{
	dsa_area_pool *fc_pool = dsa_get_address(fc_area, fc_span->pool);

	if (DsaPointerIsValid(fc_pool->spans[fc_fclass]))
	{
		dsa_area_span *fc_head = dsa_get_address(fc_area,
											  fc_pool->spans[fc_fclass]);

		fc_head->prevspan = fc_span_pointer;
	}
	fc_span->prevspan = InvalidDsaPointer;
	fc_span->nextspan = fc_pool->spans[fc_fclass];
	fc_pool->spans[fc_fclass] = fc_span_pointer;
	fc_span->fclass = fc_fclass;
}

/*
 * 从该进程创建或附加的区域中断开连接。
 */
void dsa_detach(dsa_area *fc_area)
{
	int			fc_i;

	/* 从所有段中断开连接。 */
	for (fc_i = 0; fc_i <= fc_area->high_segment_index; ++fc_i)
		if (fc_area->segment_maps[fc_i].segment != NULL)
			dsm_detach(fc_area->segment_maps[fc_i].segment);

	/*
	 * 请注意，“断开连接”（= 从 DSM 段中断开连接）不包括“释放”（= 调整引用计数）。合并这些操作是不错的，但客户端代码可能因为错误路径而永远不会调用 dsa_detach，而在任何特定段上的断开连接钩子太晚，无法在不冒着在非错误路径中出现“泄漏”警告的情况下解除其他段在区域中的连接。
	 */

	/* 释放后端本地区域对象。 */
	pfree(fc_area);
}

/*
 * 从包含它的箱子中取消链接一个段。
 */
static void fc_unlink_segment(dsa_area *fc_area, dsa_segment_map *fc_segment_map)
{
	if (fc_segment_map->header->prev != DSA_SEGMENT_INDEX_NONE)
	{
		dsa_segment_map *fc_prev;

		fc_prev = get_segment_by_index(fc_area, fc_segment_map->header->prev);
		fc_prev->header->next = fc_segment_map->header->next;
	}
	else
	{
		Assert(fc_area->control->segment_bins[fc_segment_map->header->bin] ==
			   get_segment_index(fc_area, fc_segment_map));
		fc_area->control->segment_bins[fc_segment_map->header->bin] =
			fc_segment_map->header->next;
	}
	if (fc_segment_map->header->next != DSA_SEGMENT_INDEX_NONE)
	{
		dsa_segment_map *fc_next;

		fc_next = get_segment_by_index(fc_area, fc_segment_map->header->next);
		fc_next->header->prev = fc_segment_map->header->prev;
	}
}

/*
 * 查找可以满足“npages”连续内存请求的段，如果找不到则返回 NULL。这可能涉及附加到之前未附加的段，以便我们可以查询它们的空闲页面映射。
 */
static dsa_segment_map * get_best_segment(dsa_area *fc_area, size_t fc_npages)
{
	size_t		fc_bin;

	Assert(LWLockHeldByMe(DSA_AREA_LOCK(fc_area)));
	fc_check_for_freed_segments_locked(fc_area);

	/*
	 * 从第一个可能具有足够连续页面的箱子开始搜索。
	 */
	for (fc_bin = contiguous_pages_to_segment_bin(fc_npages);
		 fc_bin < DSA_NUM_SEGMENT_BINS;
		 ++fc_bin)
	{
		/*
		 * 此箱中任何段应该具有的最小连续大小。如果我们看到的段更少，我们将重新归类。
		 */
		size_t		fc_threshold = (size_t) 1 << (fc_bin - 1);
		dsa_segment_index fc_segment_index;

		/* 在此箱中搜索具有足够连续空间的段。 */
		fc_segment_index = fc_area->control->segment_bins[fc_bin];
		while (fc_segment_index != DSA_SEGMENT_INDEX_NONE)
		{
			dsa_segment_map *fc_segment_map;
			dsa_segment_index fc_next_segment_index;
			size_t		fc_contiguous_pages;

			fc_segment_map = get_segment_by_index(fc_area, fc_segment_index);
			fc_next_segment_index = fc_segment_map->header->next;
			fc_contiguous_pages = fpm_largest(fc_segment_map->fpm);

			/* 对于请求来说不够，但对这个箱子仍然足够。 */
			if (fc_contiguous_pages >= fc_threshold && fc_contiguous_pages < fc_npages)
			{
				fc_segment_index = fc_next_segment_index;
				continue;
			}

			/* 如果它不再在适当的箱中，则重新归类。 */
			if (fc_contiguous_pages < fc_threshold)
			{
				fc_rebin_segment(fc_area, fc_segment_map);

				/*
				 * 但可以继续查看这是否足以满足这个
				 * 请求....
				 */
			}

			/* 检查我们是否完成。 */
			if (fc_contiguous_pages >= fc_npages)
				return fc_segment_map;

			/* 继续搜索相同的箱。 */
			fc_segment_index = fc_next_segment_index;
		}
	}

	/* 未找到。 */
	return NULL;
}

/*
 * 创建一个新的段，能够处理至少请求的页面。如果请求的总大小限制或允许的最大段数量将被超出，则返回 NULL。
 */
static dsa_segment_map * fc_make_new_segment(dsa_area *fc_area, size_t fc_requested_pages)
{
	dsa_segment_index fc_new_index;
	size_t		fc_metadata_bytes;
	size_t		fc_total_size;
	size_t		fc_total_pages;
	size_t		fc_usable_pages;
	dsa_segment_map *fc_segment_map;
	dsm_segment *fc_segment;

	Assert(LWLockHeldByMe(DSA_AREA_LOCK(fc_area)));

	/* 查找未使用的段槽（目前线性查找）。 */
	for (fc_new_index = 1; fc_new_index < DSA_MAX_SEGMENTS; ++fc_new_index)
	{
		if (fc_area->control->segment_handles[fc_new_index] == DSM_HANDLE_INVALID)
			break;
	}
	if (fc_new_index == DSA_MAX_SEGMENTS)
		return NULL;

	
/*
	 * 如果总大小限制已经超过，那么我们提前退出，避免下面无符号表达式中的算术溢出。
	 */
	if (fc_area->control->total_segment_size >=
		fc_area->control->max_total_segment_size)
		return NULL;

	/*
	 * 尺寸应该至少与请求的大小相同，并且至少足够大
	 * 以遵循每次创建新段时大约翻倍总存储的几何级数。我们使用几何
	 * 增长是因为底层的DSM系统并不设计用于大量
	 * 段（否则我们可能会考虑仅为每个大分配和每个超级块使用一个
	 * DSM段，那样我们就不需要使用FreePageManager）。
	 *
	 * 我们首先决定总段大小，以便生成整齐的
	 * 2的幂大小的段。如果我们将来迁移到大页，这是一个良好的特性。然后我们回推可容纳的
	 * 页数。
	 */
	fc_total_size = DSA_INITIAL_SEGMENT_SIZE *
		((size_t) 1 << (fc_new_index / DSA_NUM_SEGMENTS_AT_EACH_SIZE));
	fc_total_size = Min(fc_total_size, DSA_MAX_SEGMENT_SIZE);
	fc_total_size = Min(fc_total_size,
					 fc_area->control->max_total_segment_size -
					 fc_area->control->total_segment_size);

	fc_total_pages = fc_total_size / FPM_PAGE_SIZE;
	fc_metadata_bytes =
		MAXALIGN(sizeof(dsa_segment_header)) +
		MAXALIGN(sizeof(FreePageManager)) +
		sizeof(dsa_pointer) * fc_total_pages;

	/* 添加填充以达到下一个页面边界。 */
	if (fc_metadata_bytes % FPM_PAGE_SIZE != 0)
		fc_metadata_bytes += FPM_PAGE_SIZE - (fc_metadata_bytes % FPM_PAGE_SIZE);
	if (fc_total_size <= fc_metadata_bytes)
		return NULL;
	fc_usable_pages = (fc_total_size - fc_metadata_bytes) / FPM_PAGE_SIZE;
	Assert(fc_metadata_bytes + fc_usable_pages * FPM_PAGE_SIZE <= fc_total_size);

	/* 看看这是否足够... */
	if (fc_requested_pages > fc_usable_pages)
	{
		/*
		 * 我们将创建一个奇数大小的段，从请求的
		 * 页数向前工作。
		 */
		fc_usable_pages = fc_requested_pages;
		fc_metadata_bytes =
			MAXALIGN(sizeof(dsa_segment_header)) +
			MAXALIGN(sizeof(FreePageManager)) +
			fc_usable_pages * sizeof(dsa_pointer);

		/* 添加填充以达到下一个页面边界。 */
		if (fc_metadata_bytes % FPM_PAGE_SIZE != 0)
			fc_metadata_bytes += FPM_PAGE_SIZE - (fc_metadata_bytes % FPM_PAGE_SIZE);
		fc_total_size = fc_metadata_bytes + fc_usable_pages * FPM_PAGE_SIZE;

		/* 对于 dsa_pointer 的寻址方案来说，这是否太大？ */
		if (fc_total_size > DSA_MAX_SEGMENT_SIZE)
			return NULL;

		/* 这是否会超过限制？ */
		if (fc_total_size > fc_area->control->max_total_segment_size -
			fc_area->control->total_segment_size)
			return NULL;
	}

	/* 创建这个段。 */
	fc_segment = dsm_create(fc_total_size, 0);
	if (fc_segment == NULL)
		return NULL;
	dsm_pin_segment(fc_segment);
	if (fc_area->mapping_pinned)
		dsm_pin_mapping(fc_segment);

	/* 将句柄存储在共享内存中，以便通过索引找到。 */
	fc_area->control->segment_handles[fc_new_index] =
		dsm_segment_handle(fc_segment);
	/* 跟踪该区域历史中最高的段索引。 */
	if (fc_area->control->high_segment_index < fc_new_index)
		fc_area->control->high_segment_index = fc_new_index;
	/* 跟踪该后端曾经映射过的最高段索引。 */
	if (fc_area->high_segment_index < fc_new_index)
		fc_area->high_segment_index = fc_new_index;
	/* 跟踪所有段的总大小。 */
	fc_area->control->total_segment_size += fc_total_size;
	Assert(fc_area->control->total_segment_size <=
		   fc_area->control->max_total_segment_size);

	/* 为该后端中的这个段构建段映射。 */
	fc_segment_map = &fc_area->segment_maps[fc_new_index];
	fc_segment_map->segment = fc_segment;
	fc_segment_map->mapped_address = dsm_segment_address(fc_segment);
	fc_segment_map->header = (dsa_segment_header *) fc_segment_map->mapped_address;
	fc_segment_map->fpm = (FreePageManager *)
		(fc_segment_map->mapped_address +
		 MAXALIGN(sizeof(dsa_segment_header)));
	fc_segment_map->pagemap = (dsa_pointer *)
		(fc_segment_map->mapped_address +
		 MAXALIGN(sizeof(dsa_segment_header)) +
		 MAXALIGN(sizeof(FreePageManager)));

	/* 设置空闲页面映射。 */
	FreePageManagerInitialize(fc_segment_map->fpm, fc_segment_map->mapped_address);
	FreePageManagerPut(fc_segment_map->fpm, fc_metadata_bytes / FPM_PAGE_SIZE,
					   fc_usable_pages);

	/* 设置段头并将其放入适当的桶中。 */
	fc_segment_map->header->magic =
		DSA_SEGMENT_HEADER_MAGIC ^ fc_area->control->handle ^ fc_new_index;
	fc_segment_map->header->usable_pages = fc_usable_pages;
	fc_segment_map->header->size = fc_total_size;
	fc_segment_map->header->bin = contiguous_pages_to_segment_bin(fc_usable_pages);
	fc_segment_map->header->prev = DSA_SEGMENT_INDEX_NONE;
	fc_segment_map->header->next =
		fc_area->control->segment_bins[fc_segment_map->header->bin];
	fc_segment_map->header->freed = false;
	fc_area->control->segment_bins[fc_segment_map->header->bin] = fc_new_index;
	if (fc_segment_map->header->next != DSA_SEGMENT_INDEX_NONE)
	{
		dsa_segment_map *fc_next =
		get_segment_by_index(fc_area, fc_segment_map->header->next);

		Assert(fc_next->header->bin == fc_segment_map->header->bin);
		fc_next->header->prev = fc_new_index;
	}

	return fc_segment_map;
}

/*
 * 检查是否有任何段被 destroy_superblock 释放，以便我们可以
 * 在这个后端中从它们分离。此函数是由
 * dsa_get_address 和 dsa_free 调用，以确保它们收到的
 * dsa_pointer 可以解析到正确的段。
 *
 * 我们想要防范的危险是，在这个后端的某个插槽中可能映射了
 * 一个旧段，而它们所拥有的 dsa_pointer 可能指向同一插槽中的
 * 某个新段。因此，这些函数必须确保处理所有指令，以从
 * 在此进程收到 dsa_pointer 时生成的已释放段中分离，
 * 然后再调用 get_segment_by_index。
 */
static void fc_check_for_freed_segments(dsa_area *fc_area)
{
	size_t		fc_freed_segment_counter;

	/*
	 * 任何已释放段的其他进程在持有 LWLock 时增加了
	 * freed_segment_counter，这必须在任何后端创建
	 * 同一插槽中新段之前，且在创建指向
	 * 新段的 dsa_pointer 之前，该指针可能会到达我们这里，
	 * 调用者必须通过适当的内存同步（某种锁或原子原语或系统
	 * 调用）将 dsa_pointer 发送到此进程。因此，我们在读取端
	 * 需要做的只是请求 freed_segment_counter 的加载
	 * 以跟随调用者对其拥有的 dsa_pointer 的加载，
	 * 我们可以确保在 dsa_pointer 到达此进程时
	 * 检测到任何已释放的段。
	 */
	pg_read_barrier();
	fc_freed_segment_counter = fc_area->control->freed_segment_counter;
	if (unlikely(fc_area->freed_segment_counter != fc_freed_segment_counter))
	{
		/* 检查所有当前映射的段以找到已释放的段。 */
		LWLockAcquire(DSA_AREA_LOCK(fc_area), LW_EXCLUSIVE);
		fc_check_for_freed_segments_locked(fc_area);
		LWLockRelease(DSA_AREA_LOCK(fc_area));
	}
}

/*
 * check_for_freed_segments() 的工作马，也直接用于
 * 已经持有区域锁的路径中。应该在获取锁后
 * 但在通过索引编号查找任何段之前调用，以确保我们
 * 取消映射任何可能之前具有与当前段相同索引的
 * 陈旧段。
 */
static void fc_check_for_freed_segments_locked(dsa_area *fc_area)
{
	size_t		fc_freed_segment_counter;
	int			fc_i;

	Assert(LWLockHeldByMe(DSA_AREA_LOCK(fc_area)));
	fc_freed_segment_counter = fc_area->control->freed_segment_counter;
	if (unlikely(fc_area->freed_segment_counter != fc_freed_segment_counter))
	{
		for (fc_i = 0; fc_i <= fc_area->high_segment_index; ++fc_i)
		{
			if (fc_area->segment_maps[fc_i].header != NULL &&
				fc_area->segment_maps[fc_i].header->freed)
			{
				dsm_detach(fc_area->segment_maps[fc_i].segment);
				fc_area->segment_maps[fc_i].segment = NULL;
				fc_area->segment_maps[fc_i].header = NULL;
				fc_area->segment_maps[fc_i].mapped_address = NULL;
			}
		}
		fc_area->freed_segment_counter = fc_freed_segment_counter;
	}
}

/*
 * 如果段不再在适当的桶中，则重新分类该段。
 */
static void fc_rebin_segment(dsa_area *fc_area, dsa_segment_map *fc_segment_map)
{
	size_t		fc_new_bin;
	dsa_segment_index fc_segment_index;

	fc_new_bin = contiguous_pages_to_segment_bin(fpm_largest(fc_segment_map->fpm));
	if (fc_segment_map->header->bin == fc_new_bin)
		return;

	/* 从其当前桶中移除它。 */
	fc_unlink_segment(fc_area, fc_segment_map);

	/* 将其推入新的桶的前面。 */
	fc_segment_index = get_segment_index(fc_area, fc_segment_map);
	fc_segment_map->header->prev = DSA_SEGMENT_INDEX_NONE;
	fc_segment_map->header->next = fc_area->control->segment_bins[fc_new_bin];
	fc_segment_map->header->bin = fc_new_bin;
	fc_area->control->segment_bins[fc_new_bin] = fc_segment_index;
	if (fc_segment_map->header->next != DSA_SEGMENT_INDEX_NONE)
	{
		dsa_segment_map *fc_next;

		fc_next = get_segment_by_index(fc_area, fc_segment_map->header->next);
		Assert(fc_next->header->bin == fc_new_bin);
		fc_next->header->prev = fc_segment_index;
	}
}
